﻿/****************************************************************************************************
	Copyright (C) 2010 RapidWebDev Organization (http://rapidwebdev.org)
	Author: Eunge, Legal Name: Jian Liu, Email: eunge.liu@RapidWebDev.org

	This program is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 2 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program.  If not, see <http://www.gnu.org/licenses/>.
 ****************************************************************************************************/

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using BaoJianSoft.Common;
using BaoJianSoft.Common.Caching;
using BaoJianSoft.Common.Data;
using BaoJianSoft.Common.Validation;
using BaoJianSoft.ExtensionModel;
using BaoJianSoft.Platform.Properties;

namespace BaoJianSoft.Platform.Linq
{
	/// <summary>
	/// The concrete data API is used for CRUD constant values implemented in linq-2-sql.
	/// The method executed add/update depends on whether identity of object is empty or not.
	/// Take an example to explain the business value of concrete data API.
	/// In a product-order system, the product may have many properties which have candidate property values for users to select.
	/// The product may have Size property with three candidate values "Big", "Medium" and "Small" and Color property with 2 candidate values "Light" and "Dark".
	/// Then we can categorize that there are 3 concrete data objects "Big", "Medium" and "Small" in concrete data type "Size" as 2 concrete data objects "Light" and "Dark" in concrete data type "Color" in concrete model provided in RapidWebDev.
	/// Generally, with Concrete Data Model, we have avoided to design database schema and implement data access API to CRUD constant values.
	/// And With extension model integrated into concrete data model, we can define the dynamic properties for concrete data based on the needs of business. 
	/// </summary>
	public class ConcreteDataApi : CachableApi, IConcreteDataApi
	{
		private IAuthenticationContext authenticationContext;

		/// <summary>
		/// Construct ConcreteDataApi
		/// </summary>
		/// <param name="authenticationContext"></param>
		public ConcreteDataApi(IAuthenticationContext authenticationContext)
		{
			this.authenticationContext = authenticationContext;
		}

		/// <summary>
		/// The method executed add/update depends on whether identity of object is empty or not.
		/// </summary>
		/// <param name="concreteDataObject">The name of concrete data should be unique in a concrete data type.</param>
		public void Save(ConcreteDataObject concreteDataObject)
		{
			Kit.NotNull(concreteDataObject, "concreteDataObject");
			Kit.NotNull(concreteDataObject.Name, "concreteDataObject.Name");
			Kit.NotNull(concreteDataObject.Type, "concreteDataObject.Type");

			using (MembershipDataContext ctx = DataContextFactory.Create<MembershipDataContext>())
			{
				ConcreteData concreteData = ctx.ConcreteDatas.FirstOrDefault(c => c.ApplicationId == authenticationContext.ApplicationId && c.Name == concreteDataObject.Name && c.Type == concreteDataObject.Type);


				// validate whether Name is unique in a concrete type.
				using (ValidationScope validationScope = new ValidationScope())
				{
					if (concreteData != null && concreteData.ConcreteDataId != concreteDataObject.Id)
					{
						validationScope.Error(Resources.DuplicateConcreteDataName, concreteData.Name);
						return;
					}
				}

				if (concreteDataObject.Id == Guid.Empty)
				{
					concreteData = ExtensionObjectFactory.Create<ConcreteData>(concreteDataObject);
					concreteData.CreatedBy = authenticationContext.User.UserId;
					concreteData.CreatedDate = DateTime.Now;
					concreteData.DeleteStatus = DeleteStatus.NotDeleted;

					ctx.ConcreteDatas.InsertOnSubmit(concreteData);
				}
				else
				{
					concreteData = ctx.ConcreteDatas.FirstOrDefault(c => c.ApplicationId == authenticationContext.ApplicationId && c.ConcreteDataId == concreteDataObject.Id);
				}

				concreteData.ApplicationId = authenticationContext.ApplicationId;
				concreteData.Type = concreteDataObject.Type;
				concreteData.DeleteStatus = concreteDataObject.DeleteStatus;
				concreteData.Description = concreteDataObject.Description;
				concreteData.Name = concreteDataObject.Name;
				concreteData.Value = concreteDataObject.Value;
				concreteData.LastUpdatedBy = authenticationContext.User.UserId;
				concreteData.LastUpdatedDate = DateTime.Now;
				concreteData.ParseExtensionPropertiesFrom(concreteDataObject);

				ctx.SubmitChanges();
				concreteDataObject.Id = concreteData.ConcreteDataId;
				concreteDataObject.CreatedBy = concreteData.CreatedBy;
				concreteDataObject.CreatedDate = concreteData.CreatedDate;
				concreteDataObject.LastUpdatedBy = concreteData.LastUpdatedBy;
				concreteDataObject.LastUpdatedDate = concreteData.LastUpdatedDate;

				base.RemoveCache(concreteDataObject.Type);
				base.AddCache(concreteDataObject.Id, concreteDataObject);
			}
		}

		/// <summary>
		/// Get concrete data by id.
		/// </summary>
		/// <param name="id"></param>
		/// <returns></returns>
		public ConcreteDataObject GetById(Guid id)
		{
			ConcreteDataObject concreteDataObject = base.GetCacheObject<ConcreteDataObject>(id);
			
			// cannot load concrete data from cache, then try to load it from database.
			if (concreteDataObject != null)
				return concreteDataObject.Clone();

			using (MembershipDataContext ctx = DataContextFactory.Create<MembershipDataContext>())
			{
				ConcreteData entity = ctx.ConcreteDatas.FirstOrDefault(c => c.ApplicationId == authenticationContext.ApplicationId && c.ConcreteDataId == id);
				concreteDataObject = new ConcreteDataObject
				{
					Id = entity.ConcreteDataId,
					Type = entity.Type,
					CreatedBy = entity.CreatedBy,
					CreatedDate = entity.CreatedDate,
					DeleteStatus = entity.DeleteStatus,
					Description = entity.Description,
					ExtensionDataTypeId = entity.ExtensionDataTypeId,
					LastUpdatedBy = entity.LastUpdatedBy,
					LastUpdatedDate = entity.LastUpdatedDate,
					Name = entity.Name,
					Value = entity.Value
				};

				concreteDataObject.ParseExtensionPropertiesFrom(entity);
				base.AddCache(concreteDataObject.Id, concreteDataObject);
			}

			return concreteDataObject;
		}

		/// <summary>
		/// Get concrete data by name.
		/// </summary>
		/// <param name="type"></param>
		/// <param name="name"></param>
		/// <returns></returns>
		public ConcreteDataObject GetByName(string type, string name)
		{
			Kit.NotNull(name, "name");

			IEnumerable<ConcreteDataObject> allConcreteDataObjects = this.FindAllByTypeInternal(type);
			var returnValue = allConcreteDataObjects.FirstOrDefault(d => d.Name == name);
			if (returnValue == null) return null;

			return returnValue.Clone();
		}

		/// <summary>
		/// Find all concrete data objects includes soft deleted in the special type sorted by Name ascendingly.
		/// </summary>
		/// <param name="type"></param>
		/// <returns></returns>
		public IEnumerable<ConcreteDataObject> FindAllByType(string type)
		{
			Kit.NotNull(type, "type");
			return this.FindAllByTypeInternal(type).Select(d => d.Clone());
		}

		private IEnumerable<ConcreteDataObject> FindAllByTypeInternal(string type)
		{
			IEnumerable<ConcreteDataObject> cachedConcreteDataObjects = base.GetCacheObject<IEnumerable<ConcreteDataObject>>(type);
			if (cachedConcreteDataObjects != null) return cachedConcreteDataObjects;

			List<ConcreteDataObject> concreteDataObjects = new List<ConcreteDataObject>();
			using (MembershipDataContext ctx = DataContextFactory.Create<MembershipDataContext>())
			{
				IEnumerable<ConcreteData> entities = ctx.ConcreteDatas.Where(c => c.ApplicationId == authenticationContext.ApplicationId && c.Type == type);
				foreach (ConcreteData entity in entities)
				{
					ConcreteDataObject concreteDataObject = new ConcreteDataObject
					{
						Id = entity.ConcreteDataId,
						Type = entity.Type,
						CreatedBy = entity.CreatedBy,
						CreatedDate = entity.CreatedDate,
						DeleteStatus = entity.DeleteStatus,
						Description = entity.Description,
						ExtensionDataTypeId = entity.ExtensionDataTypeId,
						LastUpdatedBy = entity.LastUpdatedBy,
						LastUpdatedDate = entity.LastUpdatedDate,
						Name = entity.Name,
						Value = entity.Value
					};

					concreteDataObject.ParseExtensionPropertiesFrom(entity);
					concreteDataObjects.Add(concreteDataObject);

					base.AddCache(concreteDataObject.Id, concreteDataObject);
				}

				base.AddCache(type, concreteDataObjects);
				return concreteDataObjects;
			}
		}
	}
}
